﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace EAV.Bridge
{
    public class TypeInfo
    {
        #region fields
        static Dictionary<Type, TypeInfo> _cache; 
        #endregion

        #region properties
        public string MapTypeOn { get; set; }
        public Dictionary<string, string> MapPropertiesOn { get; set; }
        public Dictionary<string, string> MapValuesOn { get; set; }
        public Dictionary<string, RelationInfo> Relations { get; set; }
        public Type Type { get; set; }

        private static Dictionary<Type, TypeInfo> Cache { get { return _cache; } } 
        #endregion

        #region constructors
        static TypeInfo()
        {
            _cache = new Dictionary<Type, TypeInfo>();
        }
        public TypeInfo()
        {
            MapPropertiesOn = new Dictionary<string, string>();
            MapValuesOn = new Dictionary<string, string>();
            Relations = new Dictionary<string, RelationInfo>();
        } 
        #endregion

        #region methods
        public static TypeInfo GetInfo(Type type)
        {
            string mapTypeOn;

            if (IsTypeMappable(type, out mapTypeOn))
            {
                if (TypeInfo.Cache.ContainsKey(type))
                {
                    return TypeInfo.Cache[type];
                }
                else
                {
                    TypeInfo.Cache.Add(type, null);

                    TypeInfo info = new TypeInfo();
                    info.Type = type;

                    info.MapTypeOn = mapTypeOn;

                    PropertyInfo[] properties = type.GetProperties();

                    foreach (PropertyInfo property in properties)
                    {
                        string mapPropertyOn;
                        string foreignKey;
                        Type relatedType;

                        if (IsPropertyMappable(property, out mapPropertyOn))
                        {
                            info.MapPropertiesOn[property.Name] = mapPropertyOn;
                            info.MapValuesOn[mapPropertyOn] = property.Name;
                        }
                        else if (IsRelation(property, out foreignKey, out relatedType))
                        {
                            if (!info.Relations.ContainsKey(property.Name))
                            {
                                RelationInfo relationInfo = new RelationInfo();
                                relationInfo.ForeignKey = foreignKey;
                                relationInfo.RelatedType = relatedType;

                                info.Relations.Add(property.Name, relationInfo);
                            }
                        }
                    }

                    TypeInfo.Cache[type] = info;

                    return info;
                }
            }

            return null;
        }         

        private static bool IsPropertyMappable(PropertyInfo property, out string mapPropertyOn)
        {
            Mappable[] mappables = (Mappable[])property.GetCustomAttributes(typeof(Mappable), true);

            return IsMappable(mappables, out mapPropertyOn);
        }
        private static bool IsTypeMappable(Type type, out string mapTypeOn)
        {
            Mappable[] mappables = (Mappable[])type.GetCustomAttributes(typeof(Mappable), true);

            return IsMappable(mappables, out mapTypeOn);
        }
        private static bool IsMappable(Mappable[] mappables, out string mapPropertyOn)
        {
            if (mappables.Length == 1)
            {
                mapPropertyOn = mappables[0].MapOn;
                return true;
            }
            else
            {
                mapPropertyOn = null;
                return false;
            }
        }
        private static bool IsRelation(PropertyInfo property, out string foreignKey, out Type type)
        {
            Relation[] relations = (Relation[])property.GetCustomAttributes(typeof(Relation), true);

            if (relations.Length == 1)
            {
                foreignKey = relations[0].ForeignKey;

                if (property.PropertyType.IsGenericType)
                {
                    type = property.PropertyType.GetGenericArguments()[0];
                }
                else
                {
                    type = property.PropertyType;
                }

                return true;
            }
            else
            {
                foreignKey = null;
                type = null;
                return false;
            }
        }
        #endregion
    }
}
